# -*- coding:utf-8 -*-

import json
import numpy as np
from igraph import Graph

# Python2 JSON.load成Unicode的坑
# https://segmentfault.com/a/1190000002779638
# def byteify(input, encoding="utf-8"):
#     if isinstance(input, dict):
#         return {byteify(key): byteify(value) for key, value in input.items()}
#     elif isinstance(input, list):
#         return [byteify(element) for element in input]
#     elif isinstance(input, unicode):
#         return input.encode(encoding)
#     else:
#         return input


# 读取json格式的配置文件
def read_json_file(filename):
    # 读取json配置文件
    f = open(filename, "r")
    root = json.load(f)
    f.close()
    # 将unicode字符串变成str
    # return byteify(root)
    return root


def read_topo_file(topo_file):
    A = np.loadtxt(topo_file)
    m, n = A.shape
    if n < 3:
        return []
    # 记录所有分支的数据
    edges = []
    for i in range(m):
        # 一条分支数据
        d = {
            "id": str(int(A[i, 0])),
            "s": str(int(A[i, 1])),
            "t": str(int(A[i, 2])),
            "r": 0.0,
        }
        if n > 3:
            d["r"] = float(A[i, 3])  # 新增风阻数据
            # 排除风阻为0的情况
            if abs(1000 * d["r"]) < 1e-3:
                continue
        if n > 4:
            d["q"] = float(A[i, 4])  # 新增风量数据
        edges.append(d)
    return edges


def read_weight_file(weights, filename):
    all_weights = []
    f = open(filename)
    for line in f:
        datas = line.strip().split()
        if len(weights) != len(datas):
            continue
        weight_datas = {}
        for i in range(len(weights)):
            data_name = weights[i]["name"]
            data_type = weights[i]["type"]
            if data_type == "int":
                weight_datas[data_name] = int(datas[i])
            elif data_type == "float":
                weight_datas[data_name] = float(datas[i])
        all_weights.append(weight_datas)
    f.close()
    return all_weights


def read_graph_datas(json_file, encoding="gbk"):
    # 读取json数据文件
    graph_datas = read_json_file(json_file)
    # 将unicode字符串变成str
    # graph_datas = byteify(graph_datas, encoding)
    # 默认不读取风量数据
    if "hasQ" not in graph_datas:
        graph_datas["hasQ"] = False
    # 如果用户指定了拓扑数据文件
    if "topo_file" in graph_datas:
        # 初始化分支数据
        if "edges" not in graph_datas:
            graph_datas["edges"] = []
        # 读取拓扑数据
        graph_datas["edges"].extend(read_topo_file(graph_datas["topo_file"]))

    # 如果用户指定了分支权重数据文件
    if "edge_weights_file" in graph_datas:
        edge = graph_datas["edge_weights_file"]
        if "weights" in edge and "file" in edge:
            if "edge_weights" not in graph_datas:
                graph_datas["edge_weights"] = []
            graph_datas["edge_weights"].extend(
                read_weight_file(edge["weights"], edge["file"])
            )

    # 如果用户指定了节点权重数据文件
    if "node_weights_file" in graph_datas:
        node = graph_datas["node_weights_file"]
        if "weights" in node and "file" in node:
            if "node_weights" not in graph_datas:
                graph_datas["node_weights"] = []
            graph_datas["node_weights"].extend(
                read_weight_file(node["weights"], node["file"])
            )
    return graph_datas


# 创建图的拓扑关系(节点和分支,以及实际id与igraph内部编号之间的映射)
def build_graph(edges, dg):
    # 收集所有的节点
    sIDs = [str(data["s"]) for data in edges]
    tIDs = [str(data["t"]) for data in edges]
    sIDs.extend(tIDs)
    # 构造节点集合(利用set去除重复编号,并排序,然后再转换成list)
    nIDs = set(sIDs)
    # 构造节点编号到igraph内部编号的映射关系
    nodes_map = dict(zip(nIDs, range(len(nIDs))))
    # 有向图增加节点(igraph节点内部编号从0开始)
    dg.add_vertices(len(nodes_map))

    # 构造分支编号到igraph内部编号的映射关系
    edges_map = {}
    # 分支的实际编号
    e_id = 0
    for data in edges:
        eID, u, v = str(data["id"]), str(data["s"]), str(data["t"])
        u, v = nodes_map[u], nodes_map[v]  # 转换成igraph内部的编号
        # 不考虑重复边!!!
        if eID not in edges_map:
            dg.add_edge(u, v)  # 增加新分支(igraph分支内部编号从0开始)
            edges_map[eID] = e_id  # 记录分支编号到igraph内部编号的映射关系
            e_id = e_id + 1
    return nodes_map, edges_map


def set_graph_ids(dg, nodes_map, edges_map):
    # 设置igraph节点的id属性数据
    for u in nodes_map:
        i = nodes_map[u]
        dg.vs[i]["id"] = u
    # 设置igraph分支的id属性数据
    for e in edges_map:
        i = edges_map[e]
        dg.es[i]["id"] = e


# 构建通风网络
def build_network(graph_datas, dg):
    if "edges" not in graph_datas:
        raise Exception('failed to build vent network. reason: lack "edges" field')
        # return False
    elif len(graph_datas["edges"]) == 0:
        # return False
        raise Exception(
            'failed to build vent network. reason: the value of "edges" field is null'
        )
    # 从词典中提取分支数据
    edges = graph_datas["edges"]
    # 构建有向图并返回节点编号、分支编号与igraph内部编号的映射关系
    nodes_map, edges_map = build_graph(edges, dg)
    # 初始化所有的分支数据默认值
    # 如果在dg.add_vertices()或dg.add_edge()之前设置的属性会出现一个问题:
    #    新增的分支的属性值为None
    # 所以这里为了保险期间,在分支和节点都被add完事之后,再给节点和分支挂载属性
    # init_graph_property(dg)
    dg.es["q"] = 0.0
    # 设置分支和节点的id属性
    set_graph_ids(dg, nodes_map, edges_map)
    # 设置分支预定义数据
    for data in edges:
        i = edges_map[str(data["id"])]  # igraph分支的内部编号
        dg.es[i]["r"] = data.get("r", 0.0)  # 设置分支的风阻(预定义)
        dg.es[i]["q"] = data.get("q", 0.0)  # 设置分支的风量(预定义)
        # dg.es[i]["adjust_r"] = data.get("adjust_r", 0.0)  # 设置分支的风量(预定义)
        # dg.es[i]["minQ"] = data.get("minQ", 0.0)  # 设置分支的风量(预定义)
        # dg.es[i]["maxQ"] = data.get("maxQ", DBL_MAX)  # 设置分支的风量(预定义)
    # 设置分支其它数据(权重、费用等)
    if "edge_weights" in graph_datas:
        for data in graph_datas["edge_weights"]:
            i = edges_map[str(data["id"])]  # igraph分支的内部编号
            for name in data:
                if name == "id":
                    continue
                dg.es[i][name] = data[name]
    # 设置分支其它数据(权重、费用等)
    if "node_weights" in graph_datas:
        for data in graph_datas["node_weights"]:
            i = nodes_map[str(data["id"])]  # igraph节点的内部编号
            for name in data:
                if name == "id":
                    continue
                dg.vs[i][name] = data[name]
    # 设置风机数据
    if "fans" in graph_datas:
        fans = graph_datas["fans"]
        for data in fans:
            i = edges_map[str(data["e_id"])]  # igraph分支的内部编号
            dg.es[i]["a0"] = data.get("a0", 0.0)
            dg.es[i]["a1"] = data.get("a1", 0.0)
            dg.es[i]["a2"] = data.get("a2", 0.0)
    # 设置固定风量数据
    if "qFixs" in graph_datas:
        qFixs = graph_datas["qFixs"]
        for data in qFixs:
            i = edges_map[str(data["e_id"])]  # igraph分支的内部编号
            dg.es[i]["fq"] = data.get("fq", 0.0)
    # 设置构筑物
    # 构筑物风阻是直接加到分支的调节风阻上的!
    # 在vno中构筑物数据并未参与计算过程!
    # todo[by dlj on 2015/2/11]:构筑物的设计后续可能需要重新考虑下!!!
    if "gates" in graph_datas:
        gates = graph_datas["gates"]
        for data in gates:
            i = edges_map[str(data["e_id"])]  # igraph分支的内部编号
            dg.es[i]["adjust_r"] = dg.es[i]["adjust_r"] + data.get("r", 0.0)
    # 设置调节参数
    if "Q" in graph_datas:
        dg["Q"] = graph_datas["Q"]
    if "q_precise" in graph_datas:
        dg["q_precise"] = graph_datas["q_precise"]
    if "h_precise" in graph_datas:
        dg["h_precise"] = graph_datas["h_precise"]
    if "maxCount" in graph_datas:
        dg["maxCount"] = graph_datas["maxCount"]
